Português

Um guia completo sobre Server Actions no Next.js 14, abordando as melhores práticas de manipulação de formulários, validação de dados, considerações de segurança e técnicas avançadas para construir aplicações web modernas.

Server Actions no Next.js 14: Dominando as Melhores Práticas de Manipulação de Formulários

O Next.js 14 introduz funcionalidades poderosas para construir aplicações web performáticas e fáceis de usar. Entre elas, as Server Actions destacam-se como uma forma transformadora de lidar com submissões de formulários e mutações de dados diretamente no servidor. Este guia oferece uma visão abrangente das Server Actions no Next.js 14, focando nas melhores práticas para manipulação de formulários, validação de dados, segurança e técnicas avançadas. Exploraremos exemplos práticos e forneceremos insights acionáveis para ajudá-lo a construir aplicações web robustas e escaláveis.

O que são as Server Actions do Next.js?

As Server Actions são funções assíncronas que são executadas no servidor e podem ser invocadas diretamente de componentes React. Elas eliminam a necessidade de rotas de API tradicionais para lidar com submissões de formulários e mutações de dados, resultando em um código simplificado, segurança aprimorada e melhor desempenho. As Server Actions são Componentes de Servidor React (RSCs), o que significa que são executadas no servidor, levando a carregamentos de página iniciais mais rápidos e melhor SEO.

Principais Benefícios das Server Actions:

Configurando seu Projeto Next.js 14

Antes de mergulhar nas Server Actions, certifique-se de que você tem um projeto Next.js 14 configurado. Se estiver começando do zero, crie um novo projeto usando o seguinte comando:

npx create-next-app@latest my-next-app

Certifique-se de que seu projeto está usando a estrutura de diretório app para aproveitar ao máximo os Server Components e as Actions.

Manipulação Básica de Formulários com Server Actions

Vamos começar com um exemplo simples: um formulário que envia dados para criar um novo item em um banco de dados. Usaremos um formulário simples com um campo de entrada e um botão de envio.

Exemplo: Criando um Novo Item

Primeiro, defina uma função de Server Action dentro do seu componente React. Esta função cuidará da lógica de submissão do formulário no servidor.

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Simula a interação com o banco de dados
  console.log('Criando item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula a latência

  console.log('Item criado com sucesso!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    await createItem(formData);
    setIsSubmitting(false);
  }

  return (
    
); }

Explicação:

Validação de Dados

A validação de dados é crucial para garantir a integridade dos dados e prevenir vulnerabilidades de segurança. As Server Actions oferecem uma excelente oportunidade para realizar a validação no lado do servidor. Essa abordagem ajuda a mitigar os riscos associados apenas à validação do lado do cliente.

Exemplo: Validando Dados de Entrada

Modifique a Server Action createItem para incluir a lógica de validação.

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  if (!name || name.length < 3) {
    throw new Error('O nome do item deve ter pelo menos 3 caracteres.');
  }

  // Simula a interação com o banco de dados
  console.log('Criando item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula a latência

  console.log('Item criado com sucesso!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Ocorreu um erro.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Explicação:

Usando Bibliotecas de Validação

Para cenários de validação mais complexos, considere usar bibliotecas de validação como:

Aqui está um exemplo usando Zod:

// app/utils/validation.ts
import { z } from 'zod';

export const CreateItemSchema = z.object({
  name: z.string().min(3, 'O nome do item deve ter pelo menos 3 caracteres.'),
});
// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  const validatedFields = CreateItemSchema.safeParse({ name });

  if (!validatedFields.success) {
    return { errors: validatedFields.error.flatten().fieldErrors };
  }

  // Simula a interação com o banco de dados
  console.log('Criando item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula a latência

  console.log('Item criado com sucesso!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Ocorreu um erro.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Explicação:

Considerações de Segurança

As Server Actions aumentam a segurança ao executar o código no servidor, mas ainda é crucial seguir as melhores práticas de segurança para proteger sua aplicação contra ameaças comuns.

Prevenindo Cross-Site Request Forgery (CSRF)

Ataques CSRF exploram a confiança que um site tem no navegador de um usuário. Para prevenir ataques CSRF, implemente mecanismos de proteção CSRF.

O Next.js lida automaticamente com a proteção CSRF ao usar Server Actions. O framework gera e valida um token CSRF para cada submissão de formulário, garantindo que a solicitação se origine da sua aplicação.

Lidando com Autenticação e Autorização de Usuários

Garanta que apenas usuários autorizados possam realizar certas ações. Implemente mecanismos de autenticação e autorização para proteger dados e funcionalidades sensíveis.

Aqui está um exemplo usando NextAuth.js para proteger uma Server Action:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';

async function createItem(formData: FormData) {
  'use server'

  const session = await getServerSession(authOptions);

  if (!session) {
    throw new Error('Não autorizado');
  }

  const name = formData.get('name') as string;

  // Simula a interação com o banco de dados
  console.log('Criando item:', name, 'pelo usuário:', session.user?.email);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula a latência

  console.log('Item criado com sucesso!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Ocorreu um erro.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Explicação:

Higienizando Dados de Entrada

Higienize os dados de entrada para prevenir ataques de Cross-Site Scripting (XSS). Ataques XSS ocorrem quando código malicioso é injetado em um site, potencialmente comprometendo os dados do usuário ou a funcionalidade da aplicação.

Use bibliotecas como DOMPurify ou sanitize-html para higienizar a entrada fornecida pelo usuário antes de processá-la em suas Server Actions.

Técnicas Avançadas

Agora que cobrimos o básico, vamos explorar algumas técnicas avançadas para usar as Server Actions de forma eficaz.

Atualizações Otimistas

As atualizações otimistas proporcionam uma melhor experiência do usuário ao atualizar imediatamente a UI como se a ação fosse bem-sucedida, mesmo antes de o servidor confirmar. Se a ação falhar no servidor, a UI é revertida para seu estado anterior.

// app/components/UpdateItemForm.tsx
'use client';

import { useState } from 'react';

async function updateItem(id: string, formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Simula a interação com o banco de dados
  console.log('Atualizando item:', id, 'com o nome:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula a latência

  // Simula uma falha (para fins de demonstração)
  const shouldFail = Math.random() < 0.5;
  if (shouldFail) {
    throw new Error('Falha ao atualizar o item.');
  }

  console.log('Item atualizado com sucesso!');
  return { name }; // Retorna o nome atualizado
}

export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  const [itemName, setItemName] = useState(initialName);

  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);

    // Atualiza a UI de forma otimista
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      // Se for bem-sucedido, a atualização já está refletida na UI através do setItemName

    } catch (error: any) {
      setErrorMessage(error.message || 'Ocorreu um erro.');
      // Reverte a UI em caso de erro
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

Nome Atual: {itemName}

{errorMessage &&

{errorMessage}

}
); }

Explicação:

Revalidando Dados

Depois que uma Server Action modifica os dados, pode ser necessário revalidar os dados em cache para garantir que a UI reflita as últimas alterações. O Next.js oferece várias maneiras de revalidar dados:

Aqui está um exemplo de revalidação de um caminho após a criação de um novo item:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { revalidatePath } from 'next/cache';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Simula a interação com o banco de dados
  console.log('Criando item:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Simula a latência

  console.log('Item criado com sucesso!');

  revalidatePath('/items'); // Revalida o caminho /items
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Ocorreu um erro.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Explicação:

Melhores Práticas para Server Actions

Para maximizar os benefícios das Server Actions, considere as seguintes melhores práticas:

Armadilhas Comuns e Como Evitá-las

Embora as Server Actions ofereçam inúmeras vantagens, existem algumas armadilhas comuns a serem observadas:

Conclusão

As Server Actions do Next.js 14 fornecem uma maneira poderosa e eficiente de lidar com submissões de formulários e mutações de dados diretamente no servidor. Seguindo as melhores práticas descritas neste guia, você pode construir aplicações web robustas, seguras e performáticas. Adote as Server Actions para simplificar seu código, aumentar a segurança e melhorar a experiência geral do usuário. Ao integrar esses princípios, considere o impacto global de suas escolhas de desenvolvimento. Garanta que seus formulários e processos de manipulação de dados sejam acessíveis, seguros e fáceis de usar para diversos públicos internacionais. Esse compromisso com a inclusividade não apenas melhorará a usabilidade de sua aplicação, mas também ampliará seu alcance e eficácia em escala global.